/*
 * platform_driver.c
 *
 * 本模块负责：
 *  1. 定义一个 yourdev_plat_info 类型的数组，列出所有平台设备条目，包括 PCIe 设备和普通物理地址设备。
 *  2. 在模块初始化时，遍历此数组：如果 is_pcie = true，则通过 pci_get_device 找到对应的 PCIe 设备，
 *     再获取 BAR0 基地址，把资源转换成 platform_device 注册；否则直接把 phys_addr + size + irq
 *     注册为 platform_device。平台设备的名字（name）要与字符驱动的匹配名字相同。
 *  3. 在模块退出时，注销所有注册过的 platform_device。
 *
 * 这样做的好处是：上层字符驱动不需要自己去扫描 PCIe，直接在 probe() 中统一通过平台设备
 * 来获取资源；如果用户通过设备树提供资源，也会优先匹配设备树，然后再走平台设备分支。
 *
 * 编译后会生成 platform_driver.ko。
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include "your_device.h"

#define MAX_PLAT_DEVICES  8   /* 根据需要扩展 */

/* ===============================================================
 * 1. 定义所有平台设备条目信息（数组形式），方便增删改查
 * ===============================================================
 */
static struct yourdev_plat_info plat_devs[MAX_PLAT_DEVICES] = {
    /* 示例：PCIe 设备条目 */
    {
        .name         = "yourdev0",
        .is_pcie      = true,
        .vendor_id    = 0x1234,       /* PCIe 厂商 ID */
        .device_id    = 0x5678,       /* PCIe 设备 ID */
        .offset       = 0x1000,       /* BAR0 基地址 + 0x1000 作为基地址使用 */
        /* 下面字段对 PCIe 条目无效 */
        .phys_addr    = 0,
        .size         = 0,
        .irq          = 0,
        .custom_param1 = 10,
        .custom_param2 = 20,
    },
    /* 示例：普通物理地址平台设备条目 */
    {
        .name         = "yourdev1",
        .is_pcie      = false,
        .vendor_id    = 0,
        .device_id    = 0,
        .offset       = 0,
        .phys_addr    = 0x40000000,   /* 物理基地址，请根据实际硬件填写 */
        .size         = 0x1000,       /* 映射长度 */
        .irq          = 45,           /* 硬中断号 */
        .custom_param1 = 30,
        .custom_param2 = 40,
    },
    /* 你可以继续添加其他条目，最多不超过 MAX_PLAT_DEVICES */
};

static int num_plat_devs = 2;  /* 上面数组里实际启用的设备数 */

/* 保存已注册的 platform_device 句柄，用于退出时注销 */
static struct platform_device *registered_pdevs[MAX_PLAT_DEVICES];

/* ===============================================================
 * 2. PCIe 设备查找并注册为 Platform Device
 * ===============================================================
 */
static int register_one_pcie_device(struct yourdev_plat_info *info, int idx)
{
    struct pci_dev *pdev = NULL;
    struct resource res[2];
    int ret, bar;
    resource_size_t base_addr;
    resource_size_t size;
    struct platform_device *platdev;
    char *pdev_name;

    /* 先找到匹配的 PCI 设备 */
    pdev = pci_get_device(info->vendor_id, info->device_id, NULL);
    if (!pdev) {
        pr_err("platform_driver: 找不到 PCIe 设备 %04x:%04x\n",
               info->vendor_id, info->device_id);
        return -ENODEV;
    }

    /* 启用设备（一般建议在需要使用 PCIe 资源时再启用） */
    ret = pci_enable_device(pdev);
    if (ret) {
        pr_err("platform_driver: pci_enable_device 失败 %d\n", ret);
        pci_dev_put(pdev);
        return ret;
    }

    /* 再启用 MSI 中断 */
    ret = pci_enable_msi(pdev);
    if (ret) {
        pr_warn("platform_driver: PCIe 设备 %s 不支持 MSI，使用传统中断\n", dev_name(&pdev->dev));
        /* 如果不支持 MSI，也可继续使用传统方式获取 IRQ：pdev->irq */
    }

    /* 假设我们只使用 BAR0 */
    bar = 0;
    base_addr = pci_resource_start(pdev, bar);
    size = pci_resource_len(pdev, bar);
    if (!base_addr || !size) {
        pr_err("platform_driver: PCIe 设备 BAR%d 资源无效\n", bar);
        pci_disable_msi(pdev);
        pci_disable_device(pdev);
        pci_dev_put(pdev);
        return -EINVAL;
    }

    /* 用户希望从 BAR0 基址 + offset 作为驱动映射地址 */
    base_addr += info->offset;
    size = info->size ? info->size : (size - info->offset);

    /* 构造资源数组：索引 0 为内存映射，索引 1 为 IRQ */
    res[0].start = base_addr;
    res[0].end   = base_addr + size - 1;
    res[0].flags = IORESOURCE_MEM;
    res[1].start = pdev->irq;
    res[1].end   = pdev->irq;
    res[1].flags = IORESOURCE_IRQ;

    /* 给 platform_device 起一个名字，可以用 info->name，也可以加 idx 后缀 */
    pdev_name = kasprintf(GFP_KERNEL, "%s", info->name);
    if (!pdev_name) {
        pci_disable_msi(pdev);
        pci_disable_device(pdev);
        pci_dev_put(pdev);
        return -ENOMEM;
    }

    /* 注册 platform device，传递私有参数（custom_param1/2） */
    platdev = platform_device_alloc(pdev_name, idx);
    kfree(pdev_name);
    if (!platdev) {
        pr_err("platform_driver: platform_device_alloc 失败\n");
        pci_disable_msi(pdev);
        pci_disable_device(pdev);
        pci_dev_put(pdev);
        return -ENOMEM;
    }

    ret = platform_device_add_resources(platdev, res, ARRAY_SIZE(res));
    if (ret) {
        pr_err("platform_driver: 添加资源失败 %d\n", ret);
        platform_device_put(platdev);
        pci_disable_msi(pdev);
        pci_disable_device(pdev);
        pci_dev_put(pdev);
        return ret;
    }

    /* 把自定义参数通过 platform_data 传给驱动 */
    ret = platform_device_add_data(platdev, info, sizeof(*info));
    if (ret) {
        pr_err("platform_driver: 添加 platform_data 失败 %d\n", ret);
        platform_device_put(platdev);
        pci_disable_msi(pdev);
        pci_disable_device(pdev);
        pci_dev_put(pdev);
        return ret;
    }

    /* 真正注册 */
    ret = platform_device_add(platdev);
    if (ret) {
        pr_err("platform_driver: platform_device_add 失败 %d\n", ret);
        platform_device_put(platdev);
        pci_disable_msi(pdev);
        pci_disable_device(pdev);
        pci_dev_put(pdev);
        return ret;
    }

    /* 保存句柄，退出时注销 */
    registered_pdevs[idx] = platdev;
    pr_info("platform_driver: 注册 PCIe Platform Device %s idx=%d base=0x%llx size=0x%llx irq=%d\n",
            info->name, idx, (unsigned long long)base_addr, (unsigned long long)size, pdev->irq);

    /* 保持 pci_dev 句柄，方便驱动里使用 */
    pci_dev_put(pdev);  /* 驱动里会再 get 回来 */

    return 0;
}

/* ===============================================================
 * 3. 普通物理地址设备注册为 Platform Device
 * ===============================================================
 */
static int register_one_phys_device(struct yourdev_plat_info *info, int idx)
{
    struct resource res[2];
    int ret;
    struct platform_device *platdev;
    char *pdev_name;

    /* 构造资源：mem + irq */
    res[0].start = info->phys_addr;
    res[0].end   = info->phys_addr + info->size - 1;
    res[0].flags = IORESOURCE_MEM;
    res[1].start = info->irq;
    res[1].end   = info->irq;
    res[1].flags = IORESOURCE_IRQ;

    /* 给 platform_device 起名字 */
    pdev_name = kasprintf(GFP_KERNEL, "%s", info->name);
    if (!pdev_name)
        return -ENOMEM;

    platdev = platform_device_alloc(pdev_name, idx);
    kfree(pdev_name);
    if (!platdev) {
        pr_err("platform_driver: platform_device_alloc 失败\n");
        return -ENOMEM;
    }

    ret = platform_device_add_resources(platdev, res, ARRAY_SIZE(res));
    if (ret) {
        pr_err("platform_driver: 添加资源失败 %d\n", ret);
        platform_device_put(platdev);
        return ret;
    }

    /* 添加私有参数 */
    ret = platform_device_add_data(platdev, info, sizeof(*info));
    if (ret) {
        pr_err("platform_driver: 添加 platform_data 失败 %d\n", ret);
        platform_device_put(platdev);
        return ret;
    }

    ret = platform_device_add(platdev);
    if (ret) {
        pr_err("platform_driver: platform_device_add 失败 %d\n", ret);
        platform_device_put(platdev);
        return ret;
    }

    registered_pdevs[idx] = platdev;
    pr_info("platform_driver: 注册 PHY Platform Device %s idx=%d phys=0x%llx size=0x%llx irq=%d\n",
            info->name, idx, (unsigned long long)info->phys_addr,
            (unsigned long long)info->size, info->irq);
    return 0;
}

/* ===============================================================
 * 4. 模块初始化 / 退出
 * ===============================================================
 */
static int __init platdrv_init(void)
{
    int i, ret;
    pr_info("platform_driver 模块加载\n");

    for (i = 0; i < num_plat_devs; i++) {
        if (plat_devs[i].is_pcie) {
            ret = register_one_pcie_device(&plat_devs[i], i);
        } else {
            ret = register_one_phys_device(&plat_devs[i], i);
        }
        if (ret) {
            pr_err("platform_driver: 注册第 %d 个设备失败, ret=%d\n", i, ret);
            /* 如果失败，可以选择继续注册下一个，或退出时回退已注册设备 */
            /* 这里选择退出并回退 */
            goto err_unregister;
        }
    }
    return 0;

err_unregister:
    /* 回退已注册的 platform_device */
    while (--i >= 0) {
        platform_device_unregister(registered_pdevs[i]);
    }
    return ret;
}

static void __exit platdrv_exit(void)
{
    int i;
    pr_info("platform_driver 模块卸载，注销所有平台设备\n");
    for (i = 0; i < num_plat_devs; i++) {
        if (registered_pdevs[i]) {
            platform_device_unregister(registered_pdevs[i]);
        }
    }
}

module_init(platdrv_init);
module_exit(platdrv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("示例作者");
MODULE_DESCRIPTION("一个示例 Platform Driver 模块，注册 PCIe + 普通物理地址设备");
